package com.hup.vd.service;

import com.hup.utils.commons.DateBuilder;
import com.hup.utils.commons.TextUtil;
import com.hup.utils.commons.exception.AppCommonException;
import com.hup.utils.commons.start.SimpleExitHandler;
import com.hup.utils.commons.start.SpringContext;
import com.hup.utils.commons.thread.ThreadUtil;
import com.hup.utils.commons.thread.component.EfficientExecutor;
import com.hup.utils.io.audio.AudioInfo;
import com.hup.utils.io.audio.AudioPlayTask.TaskListener;
import com.hup.utils.io.audio.ClipPlayTask;
import com.hup.utils.swing.frame.SingletonWindowHelper;
import com.hup.vd.constant.TaskConstant.SortType;
import com.hup.vd.constant.TaskConstant.TaskState;
import com.hup.vd.model.bo.TaskCreateReq;
import com.hup.vd.model.bo.TaskListData;
import com.hup.vd.model.config.AppConfig;
import com.hup.vd.model.config.DownloadConfig;
import com.hup.vd.model.config.TaskInfo;
import com.hup.vd.service.M3u8DownloadService.TaskViewUpdater;
import com.hup.vd.view.MainFrame;
import com.hup.vd.view.customView.TaskItemView;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Component;

import javax.swing.SwingUtilities;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.Objects;
import java.util.Vector;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 任务服务:负责任务的创建,下载
 * <p>
 * {@link MyTaskExecutor#handleUI(AtomicInteger)}
 * 轮询线程:
 * .每秒轮询一次
 * .刷新ui
 * .分发新线程执行任务
 * .每n秒保存task对象
 *
 * @author hugan
 * @date 2022/1/9
 */
@Slf4j
@Component
public class TaskService {

    private final AppConfig appConfig;
    private final DownloadConfig downloadConfig;
    private final SimpleExitHandler exitHandler;
    /**
     * 任务记录/列表
     */
    @Getter
    private TaskListData taskList;

    /**
     * 正在等待下载的任务
     */
    private ArrayBlockingQueue<TaskInfo> taskQueue;
    /**
     * 正在下载的任务
     */
    private Vector<TaskInfo> runningTask;
    /**
     * 多个任务共用的,子片段并行下载器
     */
    private ThreadPoolExecutor pieceDownloadExecutor;

    private final ClipPlayTask finishSound;

    public TaskService(AppConfig appConfig) {
        this.appConfig = appConfig;
        downloadConfig = appConfig.getDownloadConfig();
        exitHandler = SpringContext.getBean(SimpleExitHandler.class);
        initTaskList(appConfig);
        new MyTaskExecutor();

        finishSound = new ClipPlayTask("/audio/bd_netdisk3.wav", new TaskListener<ClipPlayTask>() {
            @Override
            public void onGetInfo(AudioInfo audioInfo, Exception e, ClipPlayTask task) {
                if (audioInfo != null) {
                    task.getMasterGain().setValue(-5);
                } else {
                    log.error("[任务完成]音效加载异常", e);
                }
            }
        });
    }

    private void initTaskList(AppConfig appConfig) {
        taskList = new TaskListData();
        File file = new File(TaskInfo.TASK_DIR);
        if (!file.exists() && !file.mkdirs())
            throw new IllegalArgumentException("目录创建异常:" + TaskInfo.TASK_DIR);
        String[] taskNames = file.list((dir, name) -> name.endsWith(TaskInfo.EXT));
        if (taskNames == null) return;
        for (String taskName : taskNames) {
            TaskInfo task = new TaskInfo(taskName.substring(0, taskName.length() - TaskInfo.EXT.length())).readConfigFile();
            if (task == null) continue;
            task.setShortName(new File(task.getFilePath()).getName());
            if (task.getFinishedState()) {
                task.setTaskState(TaskState.FINISHED);
                taskList.getFinished().add(task);
            } else {
                taskList.getDownloading().add(task);
            }
        }
        taskList.getDownloading().sort(SortType.get(appConfig.getDownloadSortType()).sorter);
        taskList.getFinished().sort(SortType.get(appConfig.getFinishSortType()).sorter);
    }

    /**
     * 刷新[任务列表]
     * .只针对当前list,不会新增/删除元素
     * .有条件的 更新部分字段
     *
     * @apiNote 一般是人工修改了task, 开发阶段常用; 日常也可能有少用(修改m3u8地址)
     */
    public void reloadTaskFromFile() {
        ArrayList<TaskInfo> all = new ArrayList<>();
        all.addAll(taskList.getDownloading());
        all.addAll(taskList.getFinished());
        ArrayList<TaskInfo> downloading = new ArrayList<>();
        ArrayList<TaskInfo> finished = new ArrayList<>();
        for (TaskInfo task : all) {
            TaskInfo file = task.readConfigFile();
            reloadTask(task, file);
            if (task.getFinishedState()) {
                finished.add(task);
                task.setTaskState(TaskState.FINISHED);
            } else {
                downloading.add(task);
                //[下载中]时 不能修改状态
                if (!task.isRunning()) {
                    task.setTaskState(TaskState.PAUSE);
                }
            }
        }

        taskList.getDownloading().clear();
        taskList.getDownloading().addAll(downloading);
        taskList.getFinished().clear();
        taskList.getFinished().addAll(finished);
        taskList.getDownloading().sort(SortType.get(appConfig.getDownloadSortType()).sorter);
        taskList.getFinished().sort(SortType.get(appConfig.getFinishSortType()).sorter);
    }

    /**
     * 编辑单个任务
     *
     * @param task 被修改的对象, 没运行的任务
     * @param edited 用户编辑的内容
     */
    public void reloadTaskFormEdit(TaskInfo task, TaskInfo edited) {
        if (task.isRunning()) {
            //不会发生的: 前面已拦截
            throw new AppCommonException("请先停止任务");
        }

        if (!Objects.equals(edited.getUid(), task.getUid())) {
            throw new AppCommonException("不能修改[任务id]");
        }
        reloadTask(task, edited);
        task.save();

        /*
        直接复用[重读全部task],有点浪费性能,但
        如果修改了finish字段, 则逻辑复杂 更新[列表item,列表,任务面板,task状态等]
         */
        SingletonWindowHelper.getWindow(MainFrame.class).refreshTaskData();
    }

    /**
     * 刷新任务
     *
     * @param task 被修改的对象
     */
    private void reloadTask(TaskInfo task, TaskInfo input) {
        task.setUrl(input.getUrl());
        task.setHtmlUrl(input.getHtmlUrl());
        task.setHtmlTitle(input.getHtmlTitle());
        task.setCreateTime(input.getCreateTime());
        task.setFinishTime(input.getFinishTime());
        if (!task.isRunning()) {
            task.setFilePath(input.getFilePath());
            task.setShortName(new File(task.getFilePath()).getName());

            task.setFinished(input.getFinished());
            task.setUsedTime(input.getUsedTime());
            task.setProgressMax(input.getProgressMax());
            task.setProgressValue(input.getProgressValue());
            task.setFileSizeStr(input.getFileSizeStr());
            task.setDecryptMethod(input.getDecryptMethod());
            task.setAesIvHex(input.getAesIvHex());
            task.setAesKeyBase64(input.getAesKeyBase64());
        }
    }

    /**
     * 创建任务,并添加到列表的顶部
     */
    public TaskInfo createTask(TaskCreateReq req) {
        log.info("req={}", req);
        //校验
        if (TextUtil.isBlank(req.getUrl())) throw new AppCommonException("请输入url");
        if (TextUtil.isBlank(req.getFilePath())) throw new AppCommonException("请输入路径");
        try {
            new URL(req.getUrl());
        } catch (MalformedURLException e) {
            throw new AppCommonException("url异常:" + e.getMessage());
        }
        req.setUrl(req.getUrl().trim());
        File file = new File(req.getFilePath());
        if (file.isDirectory()) throw new AppCommonException("请输入文件名");
        int extIndex = file.getName().lastIndexOf(".");
        if (extIndex == -1) throw new AppCommonException("请输入扩展名");

        //创建任务数据
        TaskInfo taskInfo = new TaskInfo();
        Date createTime = new Date();
        taskInfo.setUid(DateBuilder.format(createTime, "yyMMdd_HHmmss_SSS"));
        taskInfo.setUrl(req.getUrl());
        taskInfo.setFilePath(file.getAbsolutePath());
        File tempDirFile = taskInfo.getTempDirFile();
        if (tempDirFile.exists())
            throw new AppCommonException("任务已存在: " + tempDirFile.getAbsolutePath());

        taskInfo.setCreateTime(createTime);
        taskInfo.setShortName(new File(taskInfo.getFilePath()).getName());

        taskList.getDownloading().add(0, taskInfo);
        taskList.getDownloading().sort(SortType.get(appConfig.getDownloadSortType()).sorter);
        taskInfo.save();

        try {
            if (req.isStartNow()) switchTaskState(taskInfo, true);
        } catch (Exception e) {
            //保证任务能添加成功,不因启动异常而出现显示混乱;因为上面已经成功创建数据了
            log.info("任务启动异常:", e);
            taskInfo.setTaskErr(e);
        }
        return taskInfo;
    }

    /**
     * 重新下载
     */
    public void resetTask(TaskInfo task) {
        if (task.isRunning()) {
            throw new AppCommonException("请先停止任务");
        }
        log.info("重新下载={}", task);
        boolean finished = task.getFinishedState();
        //重置属性
        task.setCreateTime(new Date());
        task.setFinished(null);
        task.setProgressValue(0);
        task.setUsedTime(0L);
        task.setFinishTime(null);
        task.setFileSizeStr(null);
        task.setTaskState(TaskState.PAUSE);
        task.setView(null);
        if (task.getHtmlUrl() != null) {
            //重置url
            task.setUrl(task.getHtmlUrl());
            task.setHtmlUrl(null);
        }
        task.save();
        //删除文件
        FileUtils.deleteQuietly(task.getTempDirFile());
        FileUtils.deleteQuietly(new File(task.getFilePath()));
        //更新列表
        if (!finished) {
            taskList.getDownloading().remove(task);
        } else {
            taskList.getFinished().remove(task);
        }
        taskList.getDownloading().add(0, task);
        taskList.getDownloading().sort(SortType.get(appConfig.getDownloadSortType()).sorter);
        taskList.notifyTaskUpdated();
        switchTaskState(task, true);
    }

    public void deleteTask(TaskInfo task, boolean withFile) {
        log.info("task={}, withFile={}", task.getFilePath(), withFile);
        boolean deleteNow = task.getFinishedState()//已完成
                || taskQueue.remove(task)//移除成功:在队列中等待,还没执行
                || !runningTask.contains(task);//没开始的任务

        Runnable deleteCmd = () -> {
            log.info("删除={}", task);
            //删除任务记录
            FileUtils.deleteQuietly(new File(task.configPath()));
            if (withFile) {
                // 临时文件夹,下载完的文件
                FileUtils.deleteQuietly(task.getTempDirFile());
                FileUtils.deleteQuietly(new File(task.getFilePath()));
            }
            taskList.getFinished().remove(task);
            taskList.getDownloading().remove(task);
            taskList.notifyTaskUpdated();
            if (task.getProgressImage() != null) {
                task.getProgressImage().flush();
                task.setProgressImage(null);
            }
            SingletonWindowHelper.getWindow(MainFrame.class).updateTaskInfoPanel(task, true, false);
        };
        if (deleteNow) {
            deleteCmd.run();
        } else {
            //正在下载
            task.setWaitToPause(true);
            task.useView(TaskItemView::notifyTaskUpdated);
            task.setTaskEndCallback(deleteCmd);
        }
    }

    /**
     * 切换任务状态,在taskQueue中[插入,移除]任务
     *
     * @param task 仅能传入未完成的任务
     * @param isStart true-准备启动,false-停止下载
     */
    public void switchTaskState(TaskInfo task, boolean isStart) {
        log.info("task={}, isStart={}", task.getFilePath(), isStart);
        if (task.getFinishedState()) throw new AppCommonException("任务已完成");

        if (isStart) {
            if (taskQueue.contains(task)) {
                log.warn("代码异常?, 重复启动任务={}", task);
                return;
            }
            /*
            先更新状态:如果offer后才更新状态,则可能出现bug:应该后到的DOWNLOAD被实际后到的WAITING覆盖
             */
            TaskState old = task.getTaskState();
            task.setTaskState(TaskState.WAITING);
            if (!taskQueue.offer(task)) {
                task.setTaskState(old);
                throw new AppCommonException("等待队列已满:" + downloadConfig.waitTaskCount);
            } else {
                //成功才通知ui
                task.useView(TaskItemView::notifyTaskUpdated);
            }
        } else {
            boolean remove = taskQueue.remove(task);
            if (remove) {
                //移除成功:在队列中,还没执行
                task.setTaskState(TaskState.PAUSE);
            } else {
                //已不在队列,正在执行
                task.setWaitToPause(true);
            }
            task.useView(TaskItemView::notifyTaskUpdated);
        }
    }

    /**
     * 开始下载任务
     *
     * @thread 由taskExecutor发起调用
     */
    private void handleDownloadTask(TaskInfo task) {
        log.info("任务被执行={}", task);
        task.setTaskState(TaskState.DOWNLOAD);
        SwingUtilities.invokeLater(() -> {
            task.useView(TaskItemView::notifyTaskUpdated);
            SingletonWindowHelper.getWindow(MainFrame.class).updateTaskInfoPanel(task, false, true);
        });

        try {
            //初始化属性
            task.initAtTaskStart();
            //处理任务
            TaskViewUpdater taskViewUpdater = updateAll -> SwingUtilities.invokeLater(() ->
                    SingletonWindowHelper.getWindow(MainFrame.class).updateTaskInfoPanel(task, false, updateAll));
            SpringContext.getBean(M3u8DownloadService.class).doDownload(task, pieceDownloadExecutor, taskViewUpdater);

            //更新状态
            boolean finished = task.getFinishedState();
            if (!finished) {
                log.info("已暂停={}", task);
                task.setTaskState(TaskState.PAUSE);
            } else {
                log.info("已完成={}", task);
                task.setTaskState(TaskState.FINISHED);
                finishSound.play();
            }
            task.setWaitToPause(false);
            task.updateUsedTime();
            task.save();
            if (!finished) {
                SwingUtilities.invokeLater(() -> {
                    task.useView(TaskItemView::notifyTaskUpdated);
                    SingletonWindowHelper.getWindow(MainFrame.class).updateTaskInfoPanel(task, false, true);
                });
            } else {
                taskList.getDownloading().remove(task);
                taskList.getFinished().add(0, task);
                taskList.getFinished().sort(SortType.get(appConfig.getFinishSortType()).sorter);
                SwingUtilities.invokeLater(() -> taskList.notifyTaskUpdated());
            }
        } catch (Throwable e) {
            log.error("任务异常={}", task, e);
            //更新状态
            task.setTaskErr(e);
            task.setWaitToPause(false);
            task.setTaskState(TaskState.PAUSE);
            task.updateUsedTime();
            task.save();
            SwingUtilities.invokeLater(() -> {
                task.useView(TaskItemView::notifyTaskUpdated);
                SingletonWindowHelper.getWindow(MainFrame.class).updateTaskInfoPanel(task, false, true);
            });
        }
        if (task.getTaskEndCallback() != null) {
            task.getTaskEndCallback().run();
            task.setTaskEndCallback(null);//释放对象
        }
    }

    public boolean hasDownloading() {
        return !runningTask.isEmpty();
    }

    private class MyTaskExecutor {
        private final int parallelTaskCount;
        private ThreadPoolExecutor taskExecutor;

        public MyTaskExecutor() {
            parallelTaskCount = downloadConfig.parallelTaskCount;
            /*
            LinkedBlockingQueue与ArrayBlockingQueue
                https://blog.csdn.net/javazejian/article/details/77410889
             */
            taskQueue = new ArrayBlockingQueue<>(downloadConfig.waitTaskCount);
            runningTask = new Vector<>();

             /*
            任务执行器:
            最大线程数=parallelTaskCount并行下载任务数 + 1条轮询线程
            核心线程数=1, 轮询线程
             */
            taskExecutor = new EfficientExecutor(
                    1, downloadConfig.parallelTaskCount + 1,
                    0,//无等待序列,一旦有execute就会被执行或队列溢出被驳回
                    60, TimeUnit.SECONDS,//回收下载线程
                    "task-",
                    "任务执行器");
            pieceDownloadExecutor = new EfficientExecutor(
                    0, downloadConfig.parallelTaskCount * downloadConfig.parallelPieceDownloadCount,
                    0,//无等待序列,一旦有execute就会被执行或队列溢出被驳回
                    60, TimeUnit.SECONDS,//线程回收
                    "piece-",
                    "片段下载器");

            //启动轮询线程
            taskExecutor.execute(this::loopThreadTask);
        }

        /**
         * 轮询线程:
         * .每秒轮询一次
         * .刷新ui
         * .分发新线程执行任务
         */
        private void loopThreadTask() {
            Thread.currentThread().setName("task-loop");
            while (true) {
                //等待主界面启动
                MainFrame mainFrame = SingletonWindowHelper.getWindow(MainFrame.class);
                if (mainFrame == null || !mainFrame.isInited()) ThreadUtil.sleep(500);
                else break;
            }

            /*
            保存task数据到硬盘的计数器
            实现:隔n次保存一次数据,避免频繁IO
             */
            AtomicInteger saveTaskCounter = new AtomicInteger();
            long lastWakeTime;
            try {
                log.info("轮询线程.开始");
                while (!exitHandler.isExiting()) {
                    /*
                    唤醒后,执行任务
                    .先更新ui: "每秒更新"的时间要求更敏感
                    .后分发任务: 待启动的任务对延迟不敏感
                     */
                    lastWakeTime = System.currentTimeMillis();
                    handleUI(saveTaskCounter);
                    handleTaskQueue();
                    //
                    long cur = System.currentTimeMillis();
                    long sleepTime = 1000 - (cur - lastWakeTime);
                    if (sleepTime > 0) {
                        /*
                        解决sleep1000时,实际等待时间会波动+10ms左右的问题,未深究
                        lock,condition应该更适合精确唤醒
                         */
                        if (sleepTime == 1000) sleepTime = 999;
                        //log.info("sleep={}", sleepTime);
                        ThreadUtil.sleep(sleepTime);
                    }
                }
                log.info("轮询线程.正常退出");
            } catch (Exception e) {
                log.error("轮询线程.异常,线程已终止", e);
            }
        }

        private void handleUI(AtomicInteger saveTaskCounter) {
            //log.info("刷新ui.0");
            if (runningTask.isEmpty()) {
                //无任务
                saveTaskCounter.set(0);
                SingletonWindowHelper.getWindow(MainFrame.class).updateNetSpeed(null);
                return;
            }

            //有正在下载的任务
            saveTaskCounter.incrementAndGet();
            boolean needSave = false;
            if (saveTaskCounter.get() >= 5) {
                needSave = true;
                saveTaskCounter.set(0);
            }

            ArrayList<TaskInfo> tasks = new ArrayList<>(runningTask); //new对象,避免多线程访问异常
            int netSpeedTotal = 0;
            for (TaskInfo task : tasks) {
                task.updateUsedTime();
                netSpeedTotal += task.getNetSpeedCounter().doPick();
                if (needSave) task.save();
            }
            int finalNetSpeedTotal = netSpeedTotal;
            SwingUtilities.invokeLater(() -> {
                //log.info("刷新ui.1");
                for (TaskInfo task : tasks) task.useView(TaskItemView::notifyTaskUpdated);
                MainFrame mainFrame = SingletonWindowHelper.getWindow(MainFrame.class);
                mainFrame.updateNetSpeed(finalNetSpeedTotal);
            });
        }

        private void handleTaskQueue() {
            int running = runningTask.size();
            if (running >= parallelTaskCount) {
                //任务数已达到上限
                return;
            }

            TaskInfo task = taskQueue.poll();
            if (task == null) return;
            runningTask.add(task);
            ThreadUtil.sleep(5);//避免极小机率[runningTask.remove后,子线程未结束,就execute新线程,导致溢出异常]
            taskExecutor.execute(() -> {
                try {
                    handleDownloadTask(task);
                } finally {
                    runningTask.remove(task);
                }
            });
        }
    }

}
